/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.loaders;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.beans.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.swing.event.ChangeEvent;
import org.openide.TopManager;
import org.openide.NotifyDescriptor;
import org.openide.util.datatransfer.*;
import org.openide.cookies.*;
import org.openide.filesystems.*;
import org.openide.actions.ReorderAction;
import org.openide.util.HelpCtx;
import org.openide.nodes.*;
import org.openide.util.enum.QueueEnumeration;
import org.openide.util.enum.SequenceEnumeration;
import org.openide.util.enum.RemoveDuplicatesEnumeration;
import org.openide.util.enum.ArrayEnumeration;
import org.openide.util.NbBundle;
import org.openide.util.WeakListener;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
/** A folder containing data objects.
* Is actually itself a data object, whose primary (and only) file object
* is a file folder.
* <p>Has special support for determining the sorting of the folder,
* or even explicit ordering of the children.
*
* @author Jaroslav Tulach, Petr Hamernik
*/
public class DataFolder extends DataObject implements Serializable {
/** generated Serialized Version UID */
static final long serialVersionUID = -8244904281845488751L;
/** Name of property that holds children of this node. */
public static final String PROP_CHILDREN = "children"; // NOI18N
/** Name of property which decides sorting mode. */
public static final String PROP_SORT_MODE = "sortMode"; // NOI18N
/** name of extended attribute for order of children */
private static final String EA_ORDER = "OpenIDE-Folder-Order"; // NOI18N
/** name of extended attribute for order of children */
private static final String EA_SORT_MODE = "OpenIDE-Folder-SortMode"; // NOI18N
/** Name of property for order of children. */
public static final String PROP_ORDER = "order"; // NOI18N
/** Name of set with sorting options. */
public static final String SET_SORTING = "sorting"; // NOI18N
/** Icon resource string for folder node */
static final String FOLDER_ICON_BASE =
"/org/openide/resources/defaultFolder"; // NOI18N
/** name of a shadow file for a root */
private static final String ROOT_SHADOW_NAME = "Root"; // NOI18N
/** listener that contains array of children
* Also represents the folder as the node delegate.
*/
private FolderList list;
/** Sort mode for the folder Reference (SortMode)
*/
private Reference sortMode = new WeakReference (null);
/** a reference to hold softly the order for this folder */
private Reference order = new WeakReference (null);
/** Create a data folder from a folder file object.
* @param fo file folder to work on
* @exception DataObjectExistsException if there is one already
* @exception IllegalArgumentException if <code>fo</code> is not folder
*/
public DataFolder (FileObject fo)
throws DataObjectExistsException, IllegalArgumentException {
this(fo, DataLoaderPool.getFolderLoader ());
}
/** Create a data folder from a folder file object.
* @param fo file folder to work on
* @param loader data loader for this data object
* @exception DataObjectExistsException if there is one already
* @exception IllegalArgumentException if <code>fo</code> is not folder
*/
protected DataFolder (FileObject fo, DataLoader loader)
throws DataObjectExistsException, IllegalArgumentException {
this (fo, loader, true);
}
/** Create a data folder from a folder file object.
* @param fo file folder to work on
* @param loader data loader for this data object
* @exception DataObjectExistsException if there is one already
* @exception IllegalArgumentException if <code>fo</code> is not folder
*/
private DataFolder (FileObject fo, DataLoader loader, boolean attach)
throws DataObjectExistsException, IllegalArgumentException {
super (fo, loader);
if (!fo.isFolder ()) {
// not folder => throw an exception
new IllegalArgumentException ();
}
// creates object that handles all elements in array and
// assignes it to the
list = new FolderList (this, attach);
}
/** Helper method to find or create a folder of a given path.
* Tries to find such a subfolder, or creates it if it needs to.
*
* @param folder the folder to start in
* @param name a subfolder path (e.g. <code>com/mycom/testfolder</code>)
* @return a folder with the given name
* @exception IOException if the I/O fails
*/
public static DataFolder create (DataFolder folder, String name) throws IOException {
return DataFolder.findFolder (FileUtil.createFolder (folder.getPrimaryFile (), name));
}
/** Set the sort mode for the folder.
* @param mode an constant from {@link DataFolder.SortMode}
* @exception IOException if the mode cannot be set
*/
public synchronized final void setSortMode (SortMode mode) throws IOException {
SortMode old = getSortMode ();
// store the mode to properties
mode.write (this);
sortMode = new WeakReference (mode);
Order ord = getOrder ();
// first of all use order, then the comparator
ord.setComparator (mode);
list.changeComparator ();
firePropertyChange (PROP_SORT_MODE, old, sortMode);
}
/** Getter for comparator. Accessed from FolderChildren.
*/
final Comparator getComparator () {
return getOrder ();
}
/** Get the sort mode of the folder.
* @return the sort mode
*/
public final SortMode getSortMode () {
SortMode sm = (SortMode)sortMode.get ();
if (sm == null) {
sm = SortMode.read (this);
sortMode = new WeakReference (sm);
}
return sm;
}
/** Set the order of the children.
* The provided array defines
* the order of some children for the folder. Such children
* will be returned at the beginning of the array returned from
* {@link #getChildren}. If there are any other children, they
* will be appended to the array.
*
* @param arr array of data objects (children of this
* folder) to define the order; or <code>null</code> if any particular ordering should
* be cancelled
*
* @exception IOException if the order cannot be set
*
*/
public synchronized final void setOrder (DataObject[] arr) throws IOException {
Order ord = arr == null ? new Order () : new Order (this, arr);
// write the order
ord.write ();
order = new WeakReference (ord);
// adds the order before the comparator
ord.setComparator (getSortMode ());
list.changeComparator ();
firePropertyChange (PROP_ORDER, null, null);
}
/** Getter for order object.
* @return order of children
*/
private Order getOrder () {
Order o = (Order)order.get ();
if (o == null) {
o = Order.createFor (this);
o.setComparator (getSortMode ());
order = new WeakReference (o);
}
return o;
}
/** Get the children of this folder.
* @return array of children
*/
public DataObject[] getChildren () {
return list.getChildren ();
}
/** Getter for list of children.
* @param filter filter to notify about addition of new objects
*/
final ArrayList getChildrenList () {
return list.getChildrenList ();
}
/** Computes list of children asynchronously
* @param l listener to notify about the progress
* @return task that will handle the computation
*/
final RequestProcessor.Task computeChildrenList (FolderListListener l) {
return list.computeChildrenList (l);
}
/** Refreshes the list of children. Called when a child data object is
* disposed.
*/
final void refresh () {
list.refresh ();
}
/** Method that allows FolderList to fire info about change of
* objects
*/
final void fireChildrenChange (ArrayList add, ArrayList removed) {
if (!add.isEmpty() || !removed.isEmpty()) {
firePropertyChange (PROP_CHILDREN, null, null);
}
}
/** Get enumeration of children of this folder.
* @return enumeration of {@link DataObject}s
*/
public Enumeration children () {
return Collections.enumeration (getChildrenList ());
}
/** Adds a {@link CompilerCookie compilation cookie}.
*/
public Node.Cookie getCookie (Class cookie) {
// is somebody asking for folder compiler?
if (CompilerCookie.class.isAssignableFrom (cookie)) {
DataFolderCompiler c = new DataFolderCompiler (this, cookie);
if (cookie.isInstance (c)) {
return c;
}
// else go on, folder compiler does not implement such cookie
}
// does anybody wants to reorder folder?
if (org.openide.nodes.Index.class.isAssignableFrom(cookie)) {
return new Index(this);
}
// end testing
return super.getCookie (cookie);
}
/** Create node representative for this folder.
*/
protected synchronized Node createNodeDelegate () {
return new FolderNode ();
}
/** This method allows DataFolder to filter its nodes.
*
* @param filter filter for subdata objects
* @return the node delegate (without parent) for this data object
*/
final Node getClonedNodeDelegate (DataFilter filter) {
// creates normal node and filters its children
Node n = getNodeDelegate ();
return new FilterNode (n, createNodeChildren (filter));
}
/** Support method to obtain a children object that
* can be added to any {@link Node}. The provided filter can be
* used to exclude some objects from the list.
*
* @param filter filter of data objects
* @return children object representing content of this folder
*/
public final Children createNodeChildren (DataFilter filter) {
return new FolderChildren (this, filter);
}
/* Getter for delete action.
* @return true if the object can be deleted
*/
public boolean isDeleteAllowed () {
return isRenameAllowed ();
}
/* Getter for copy action.
* @return true if the object can be copied
*/
public boolean isCopyAllowed () {
return true;
}
/* Getter for move action.
* @return true if the object can be moved
*/
public boolean isMoveAllowed () {
return isRenameAllowed ();
}
/* Getter for rename action.
* @return true if the object can be renamed
*/
public boolean isRenameAllowed () {
FileObject fo = getPrimaryFile ();
return !fo.isRoot() && !fo.isReadOnly ();
}
/* Help context for this object.
* @return help context
*/
public HelpCtx getHelpCtx () {
return new HelpCtx (DataFolder.class);
}
/** Create a folder for a specified file object.
* @param fo file object
* @return folder for the file object
* @exception IllegalArgumentException if the file object is not folder
*/
public static DataFolder findFolder (FileObject fo) {
try {
return (DataFolder)DataObject.find (fo);
} catch (ClassCastException ex) {
} catch (DataObjectNotFoundException ex) {
}
// either fo is not data folder or
// has been recognized by something else => system is bad
throw new IllegalArgumentException ();
}
/** Copy this object to a folder.
* The copy of the object is required to
* be deletable and movable.
*
* @param f the folder to copy object to
* @exception IOException if something went wrong
* @return the new object
*/
protected DataObject handleCopy (DataFolder f) throws IOException {
String name = getPrimaryFile ().getName ();
// Fix 4513 name = FileUtil.findFreeFolderName (f.getPrimaryFile (), name);
FileObject newFile = FileUtil.createFolder (f.getPrimaryFile (), name);
DataFolder newFolder;
try {
newFolder = new DataFolder (newFile);
} catch (DataObjectExistsException ex) {
if (ex.getDataObject() instanceof DataFolder) {
newFolder = (DataFolder)ex.getDataObject ();
} else {
throw ex;
}
}
Enumeration en = children ();
while (en.hasMoreElements ()) {
try {
DataObject obj = (DataObject)en.nextElement ();
obj.copy (newFolder);
} catch (IOException ex) {
TopManager.getDefault ().notifyException (ex);
}
}
try {
FileUtil.copyAttributes (getPrimaryFile (), newFile);
} catch (IOException ioe) {
TopManager.getDefault ().notifyException (ioe);
}
return newFolder;
}
/* Deals with deleting of the object. Must be overriden in children.
* @exception IOException if an error occures
*/
protected void handleDelete () throws IOException {
FileObject fo = getPrimaryFile ();
FileLock lock = fo.lock ();
try {
Enumeration en = children ();
while (en.hasMoreElements ()) {
try {
DataObject obj = (DataObject)en.nextElement ();
if (obj.isValid ()) {
obj.delete ();
}
} catch (IOException ex) {
TopManager.getDefault ().notifyException (ex);
}
}
fo.delete (lock);
} finally {
lock.releaseLock ();
}
}
/* Handles renaming of the object.
* Must be overriden in children.
*
* @param name name to rename the object to
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected FileObject handleRename (String name) throws IOException {
FileObject fo = getPrimaryFile ();
FileLock lock = fo.lock ();
try {
fo.rename (lock, name, null);
} finally {
lock.releaseLock ();
}
return fo;
}
/* Handles move of the object. Must be overriden in children.
*
* @param df target data folder
* @return new primary file of the object
* @exception IOException if an error occures
*/
protected FileObject handleMove (DataFolder df) throws IOException {
FileObject fo = getPrimaryFile ();
FileLock lock = fo.lock ();
DataFolder newFolder = null;
try {
String name = getPrimaryFile ().getName ();
name = FileUtil.findFreeFolderName (df.getPrimaryFile (), name);
FileObject newFile = df.getPrimaryFile ().createFolder (name);
// temporary folder for moving into
newFolder = new DataFolder (newFile);
Enumeration en = children ();
while (en.hasMoreElements ()) {
try {
DataObject obj = (DataObject)en.nextElement ();
obj.move (newFolder);
} catch (IOException ex) {
TopManager.getDefault ().notifyException (ex);
}
}
try {
FileUtil.copyAttributes (fo, newFile);
} catch (IOException ioe) {
TopManager.getDefault ().notifyException (ioe);
}
fo.delete (lock);
// disposes temporary folder and places itself instead of it
newFolder.dispose ();
newFolder.list.reassign(this);
list = newFolder.list;
// changes primary file of this folder to the new folder
return newFile;
} catch (IOException e) {
if (newFolder != null) {
/* in the case there would be the new folder - with a FolderList.
* The FolderList is without a FileChangeListener!!!
*/
newFolder.list.reassign(newFolder);
}
throw e;
} finally {
lock.releaseLock ();
}
}
/* Creates new object from template.
* @param f folder to create object in
* @return new data object
* @exception IOException if an error occured
*/
protected DataObject handleCreateFromTemplate (
DataFolder f, String name
) throws IOException {
if (name == null) name = getPrimaryFile ().getName ();
FileObject newFile = f.getPrimaryFile ().createFolder (name);
DataFolder newFolder = new DataFolder (newFile);
Enumeration en = children ();
while (en.hasMoreElements ()) {
try {
DataObject obj = (DataObject)en.nextElement ();
obj.copy (newFolder);
} catch (IOException ex) {
TopManager.getDefault ().notifyException (ex);
}
}
try {
FileUtil.copyAttributes (getPrimaryFile (), newFile);
} catch (IOException ioe) {
TopManager.getDefault ().notifyException (ioe);
}
DataObject.setTemplate (newFile, false);
return newFolder;
}
/** Creates shadow for this object in specified folder (overridable in subclasses).
* <p>The default
* implementation creates a reference data shadow and pastes it into
* the specified folder.
*
* @param f the folder to create a shortcut in
* @return the shadow
*/
protected DataShadow handleCreateShadow (DataFolder f) throws IOException {
String name;
if (getPrimaryFile ().isRoot ()) {
name = FileUtil.findFreeFileName (
f.getPrimaryFile (), ROOT_SHADOW_NAME, DataShadow.SHADOW_EXTENSION
);
} else {
name = null;
}
return DataShadow.create (f, name, this);
}
/** Support for index cookie for folder nodes.
*/
public static class Index extends org.openide.nodes.Index.Support {
/** Asociated data folder */
private DataFolder df;
/** node to be associated with */
private Node node;
/** change listener */
private Listener listener;
/** Create an index cookie associated with a data folder.
* @param df the data folder
*/
public Index(final DataFolder df) {
this (df, df.getNodeDelegate ());
}
/** Create an index cookie associated with a data folder.
* @param df the data folder
* @param node node to be associated with. subnodes of this node will be returned, etc.
*/
public Index(final DataFolder df, Node node) {
this.df = df;
this.node = node;
listener = new Listener ();
node.addNodeListener (WeakListener.node (listener, node));
}
/* Returns the index of given node.
* @param node Node to find index of.
* @return Index of the node, -1 if no such node was found.
*/
public int indexOf (final Node node) {
Node[] nodes = getNodes();
for (int i = 0; i < nodes.length; i++) {
if (node.equals (nodes[i])) return i;
}
// not found
return -1;
}
/* Returns count of the nodes.
*/
public int getNodesCount () {
return node.getChildren().getNodesCount();
}
/* Returns array of subnodes
* @return array of subnodes
*/
public Node[] getNodes () {
return node.getChildren().getNodes();
}
/* Reorders all children with given permutation.
* @param perm permutation with the length of current nodes
* @exception IllegalArgumentException if the perm is not
* valid permutation
*/
public void reorder (int[] perm) {
// testing
/*System.out.println ("Permutation: ");
for (int i = 0; i < perm.length; i++) {
System.out.println ("From " + i + " to " + perm[i]);
}*/
DataObject[] curObjs = df.getChildren();
//testing
/* System.out.println ("Folder children: ");
for (int i = 0; i < curObjs.length; i++) {
System.out.println (i + " " + curObjs[i].toString());
} */
DataObject[] newObjs = new DataObject[curObjs.length];
// permute data objects
int targetIndex;
for (int i = 0; i < curObjs.length; i++) {
targetIndex = perm[i];
if (newObjs[targetIndex] != null)
throw new IllegalArgumentException("Bad input permutation"); // NOI18N
newObjs[targetIndex] = curObjs[i];
}
try {
df.setOrder(newObjs);
} catch (java.io.IOException ex) {
TopManager.getDefault().notify(
new NotifyDescriptor.Exception(ex,
DataObject.getString("EXC_ReorderFailed")));
}
}
/* Invokes a dialog for reordering subnodes.
*/
public void reorder () {
IndexedCustomizer ic = new IndexedCustomizer();
ic.setObject(this);
// turn off immediate reorder so that children are reordered
// at once when closing the dialog
ic.setImmediateReorder(false);
ic.show();
}
/** Fires notification about reordering to all
* registered listeners.
*/
void fireChangeEventAccess () {
fireChangeEvent (new ChangeEvent (this));
}
/** Listener to change of children of the folder.
*/
private final class Listener extends Object implements NodeListener {
/** Change of children?
*/
public void propertyChange (PropertyChangeEvent ev) {
}
/** Fired when the node is deleted.
* @param ev event describing the node
*/
public void nodeDestroyed(NodeEvent ev) {
}
/** Fired when the order of children is changed.
* @param ev event describing the change
*/
public void childrenReordered(NodeReorderEvent ev) {
fireChangeEventAccess ();
}
/** Fired when a set of children is removed.
* @param ev event describing the action
*/
public void childrenRemoved(NodeMemberEvent ev) {
fireChangeEventAccess ();
}
/** Fired when a set of new children is added.
* @param ev event describing the action
*/
public void childrenAdded(NodeMemberEvent ev) {
fireChangeEventAccess ();
}
} // end of Listener
} // end of Index inner class
/** Type-safe enumeration of sort modes for data folders.
*/
public abstract static class SortMode extends Object implements Comparator {
/** Objects are unsorted. */
public static final SortMode NONE = new FolderComparator (FolderComparator.NONE);
/** Objects are sorted by their names. */
public static final SortMode NAMES = new FolderComparator (FolderComparator.NAMES);
/** Objects are sorted by their types and then by names. */
public static final SortMode CLASS = new FolderComparator (FolderComparator.CLASS);
/** Folders go first (sorted by name) followed by non-folder
* objects sorted by name.
*/
public static final SortMode FOLDER_NAMES = new FolderComparator (FolderComparator.FOLDER_NAMES);
/** Method to write the sort mode to a folder's attributes.
* @param folder folder write this mode to
*/
void write (DataFolder f) throws IOException {
if (f.getPrimaryFile ().getFileSystem ().isReadOnly ()) return; // cannot write to read-only FS
String x;
if (this == FOLDER_NAMES) x = null;
else if (this == NAMES) x = "N"; // NOI18N
else if (this == CLASS) x = "C"; // NOI18N
else x = "O"; // NOI18N
f.getPrimaryFile ().setAttribute (EA_SORT_MODE, x);
}
/** Reads sort mode for given folder.
*/
static SortMode read (DataFolder f) {
String x = (String)f.getPrimaryFile ().getAttribute (EA_SORT_MODE);
if (x == null || x.length () != 1) return FOLDER_NAMES;
char c = x.charAt (0);
switch (c) {
case 'N': return NAMES;
case 'C': return CLASS;
default: return NONE;
}
}
}
/** Class that represents order of child data objects.
*/
private static final class Order extends Object implements Comparator {
/** map of primary files of objects to their index (FileObject, Integer)
* @associates Integer*/
private HashMap order;
/** comparator to use when comparing files not in the map */
private transient Comparator comp;
/** file to store data in */
private FileObject folder;
/** Constructor.
* @param folder the folder to create order for
* @param arr array that defines the order
*/
public Order (DataFolder folder, DataObject[] arr) {
order = new HashMap (arr.length);
// each object only once
RemoveDuplicatesEnumeration en = new RemoveDuplicatesEnumeration (
new ArrayEnumeration (arr)
);
int i = 0;
while (en.hasMoreElements ()) {
DataObject obj = (DataObject)en.nextElement ();
if (obj.getFolder () == folder) {
// object for my folder
FileObject fo = obj.getPrimaryFile ();
order.put (fo, new Integer (i++));
}
}
this.folder = folder.getPrimaryFile ();
}
/** Constructor.
* @param s set with ordering
*/
private Order (HashMap s) {
order = s;
}
/** Constructs empty order.
*/
public Order () {
order = new HashMap (1);
}
/** Sets the right comparator.
*/
void setComparator (Comparator c) {
comp = c;
}
/** Compares two data object or two nodes.
*/
public int compare (Object o1, Object o2) {
DataObject obj1;
DataObject obj2;
if (o1 instanceof Node) {
obj1 = (DataObject)((Node)o1).getCookie (DataObject.class);
obj2 = (DataObject)((Node)o2).getCookie (DataObject.class);
} else {
obj1 = (DataObject)o1;
obj2 = (DataObject)o2;
}
Integer i1 = (Integer)order.get (obj1.getPrimaryFile ());
Integer i2 = (Integer)order.get (obj2.getPrimaryFile ());
if (i1 == null) {
if (i2 != null) return 1;
// compare by the provided comparator
return comp.compare (obj1, obj2);
} else {
if (i2 == null) return -1;
// compare integers
if (i1.intValue () == i2.intValue ()) return 0;
if (i1.intValue () < i2.intValue ()) return -1;
return 1;
}
}
/** Stores the order to files.
*/
public void write () throws IOException {
write (false);
}
/** Clears an order.
*/
public void clear () throws IOException {
write (true);
}
/** Stores the order to files.
* @param clear true if we should only clear the order
*/
private void write (boolean clear) throws IOException {
if (folder.getFileSystem ().isReadOnly ()) return; // cannot write to read-only FS
//System.out.println ("Writing order, clear? " + clear); // NOI18N
if (clear || order.isEmpty ()) {
// if we should clear the object or the order is empty
folder.setAttribute (EA_ORDER, null);
} else {
java.util.Iterator it = order.entrySet ().iterator ();
int size = order.size ();
String[] names = new String[size];
String[] exts = new String[size];
while (it.hasNext ()) {
Map.Entry en = (Map.Entry)it.next ();
FileObject fo = (FileObject)en.getKey ();
int indx = ((Integer)en.getValue ()).intValue ();
names[indx] = fo.getName ();
exts[indx] = fo.getExt ();
}
folder.setAttribute (EA_ORDER, new String[][] { names, exts });
}
}
/** Creates order for given folder object.
* @param f the folder
* @return the order
*/
public static Order createFor (DataFolder f) {
FileObject folder = f.getPrimaryFile ();
String[][] namesExts = (String[][])folder.getAttribute (EA_ORDER);
if (namesExts == null) {
// empty order
return new Order ();
}
String[] names = namesExts[0];
String[] exts = namesExts[1];
if (names == null || exts == null || names.length != exts.length) {
// empty order
return new Order ();
}
HashMap set = new HashMap (names.length);
for (int i = 0; i < names.length; i++) {
FileObject fo = folder.getFileObject (names[i], exts[i]);
if (fo != null) {
// found
set.put (fo, new Integer (i));
}
}
return new Order (set);
}
}
/** Node for a folder.
*/
public class FolderNode extends DataNode {
/** Create a folder node with some children.
* @param ch children to use for the node
*/
public FolderNode (Children ch) {
super (DataFolder.this, ch);
setIconBase(FOLDER_ICON_BASE);
}
/** Create a folder node with default folder children.
*/
protected FolderNode () {
super (DataFolder.this, new FolderChildren (DataFolder.this));
setIconBase(FOLDER_ICON_BASE);
}
/** Renames the folder, but forbids names with a space.
* @param folderName name to rename folder to
* @exception IllegalArgumentException if the name is not valid
*/
public void setName (String folderName) {
if (!Utilities.isJavaIdentifier (folderName)) {
throw new IllegalArgumentException (
java.text.MessageFormat.format (DataObject.getString("EXC_WrongName"), new Object[] { folderName} )
);
}
super.setName (folderName);
}
/** Adds properties for sorting.
* @return the augmented property sheet
*/
protected Sheet createSheet () {
Sheet s = super.createSheet ();
Sheet.Set ss = new Sheet.Set ();
ss.setName (SET_SORTING);
ss.setDisplayName (DataObject.getString ("PROP_sorting"));
ss.setShortDescription (DataObject.getString ("HINT_sorting"));
Node.Property p;
p = new PropertySupport.ReadWrite (
PROP_SORT_MODE, SortMode.class,
DataObject.getString("PROP_sort"),
DataObject.getString("HINT_sort")
) {
public Object getValue () {
return DataFolder.this.getSortMode ();
}
public void setValue (Object o) throws InvocationTargetException {
try {
DataFolder.this.setSortMode ((SortMode)o);
} catch (IOException ex) {
throw new InvocationTargetException (ex);
}
}
public java.beans.PropertyEditor getPropertyEditor () {
return new SortModeEditor ();
}
};
ss.put (p);
s.put (ss);
return s;
}
/** New type for creating new subfolder.
* @return array with one element
*/
public NewType[] getNewTypes () {
if (getPrimaryFile ().isReadOnly ()) {
// no new types
return new NewType[0];
} else {
return new NewType[] { new NewFolder () };
}
}
/** Look for one or more nodes with data object cookie in this transferable.
* @param t the transferable to probe
* @param actions copy-style or move-style
* @return a list of data objects (nonempty), or <code>null</code> if these are not present
*/
private DataObject[] findDataObjectsInTransferable (Transferable t, int actions) {
Node[] nodes = NodeTransfer.nodes (t, actions);
//System.err.println ("NodeTransfer.nodes=" + nodes);
if (nodes == null) return null;
//System.err.println ("nodes: " + nodes.length);
//for (int i1 = 0; i1 < nodes.length; i1++)
// System.err.println ("\t" + nodes[i1].getDisplayName ());
DataObject[] dobs = new DataObject[nodes.length];
for (int i = 0; i < nodes.length; i++) {
DataObject dob = (DataObject) nodes[i].getCookie (DataObject.class);
if (dob == null)
return null;
else
dobs[i] = dob;
}
return dobs;
}
/** May add some paste types for objects being added to folders.
* May move data objects; copy them; create links for them; instantiate
* them as templates; serialize instances; or create instance data objects
* from instances, according to the abilities of the transferable.
*
* @param t transferable to use
* @param s list of {@link PasteType}s
*/
protected void createPasteTypes (Transferable t, java.util.List s) {
super.createPasteTypes (t, s);
if (!getPrimaryFile ().isReadOnly ()) {
boolean ok;
int i;
// Permit moving of multiple objects, provided that they all declare themselves movable.
DataObject[] objs = findDataObjectsInTransferable (t, NodeTransfer.MOVE);
if (objs != null) {
//System.err.println ("Got movable objs: " + objs.length);
//for (int i1 = 0; i1 < objs.length; i1++)
// System.err.println ("\t" + objs[i1].getName ());
ok = true;
for (i = 0; i < objs.length; i++) {
if (! objs[i].isMoveAllowed ()) {
ok = false;
break;
}
}
//System.err.println ("[move] ok=" + ok);
if (ok) {
// add move paste type, the type then clears clipboard
s.add (new Paste ("PT_move", objs, true, "move") { // NOI18N
public void handle (DataObject obj2) throws IOException {
obj2.move (DataFolder.this);
}
});
}
}
// Now try copy-style pastes.
// [PENDING] should NodeTransfer.COPY be used instead?
objs = findDataObjectsInTransferable (t, NodeTransfer.CLIPBOARD_COPY);
if (objs != null) {
//System.err.println ("Got copyable objs: " + objs.length);
//for (int i2 = 0; i2 < objs.length; i2++)
// System.err.println ("\t" + objs[i2].getName ());
ok = true;
for (i = 0; i < objs.length; i++) {
if (! objs[i].isCopyAllowed ()) {
ok = false;
break;
}
}
//System.err.println ("[copy] ok=" + ok);
if (ok) {
// copy all objects to this folder
s.add (new Paste ("PT_copy", objs, false, "copy") { // NOI18N
public void handle (DataObject obj2) throws IOException {
obj2.copy (DataFolder.this);
}
});
}
ok = true;
for (i = 0; i < objs.length; i++) {
if (! objs[i].isTemplate ()) {
ok = false;
break;
}
}
//System.err.println ("[template] ok=" + ok);
if (ok) {
// instantiate template(s) with default name
s.add (new Paste ("PT_instantiate", objs, false, "instantiate") { // NOI18N
public void handle (DataObject obj2) throws IOException {
obj2.createFromTemplate (DataFolder.this);
}
});
}
ok = true;
for (i = 0; i < objs.length; i++) {
if (! objs[i].isShadowAllowed ()) {
ok = false;
break;
}
}
//System.err.println ("[shadow] ok=" + ok);
if (ok) {
// instantiate template
s.add (new Paste ("PT_shadow", objs, false, "shadow") { // NOI18N
public void handle (DataObject obj2) throws IOException {
obj2.createShadow (DataFolder.this);
}
});
}
}
// These should only accept single-node transfers, since they require dialogs.
Node node = NodeTransfer.node (t, NodeTransfer.CLIPBOARD_COPY);
// lastly try special cookies
if (node != null) {
try {
InstanceCookie cookie = (InstanceCookie)node.getCookie (InstanceCookie.class);
if (cookie != null && java.io.Serializable.class.isAssignableFrom (cookie.instanceClass ())) {
s.add (new SerializePaste (cookie));
s.add (new InstantiatePaste (cookie));
}
} catch (IOException e) {
} catch (ClassNotFoundException e) {
}
}
}
}
} // end of FolderNode
/** New type for creation of new folder.
*/
private final class NewFolder extends NewType {
/** Display name for the creation action. This should be
* presented as an item in a menu.
*
* @return the name of the action
*/
public String getName() {
return DataObject.getString ("CTL_NewFolder");
}
/** Help context for the creation action.
* @return the help context
*/
public HelpCtx getHelpCtx() {
return new HelpCtx (NewFolder.class);
}
/** Create the object.
* @exception IOException if something fails
*/
public void create () throws IOException {
NotifyDescriptor.InputLine input = new NotifyDescriptor.InputLine (
DataObject.getString ("CTL_NewFolderName"), DataObject.getString ("CTL_NewFolderTitle")
);
input.setInputText (DataObject.getString ("CTL_NewFolderValue"));
if (TopManager.getDefault ().notify (input) == NotifyDescriptor.OK_OPTION) {
String folderName = input.getInputText ();
if ("".equals (folderName)) return; // empty name = cancel // NOI18N
FileObject folder = getPrimaryFile ();
int dotPos = -1;
while ((dotPos = folderName.indexOf (".")) != -1) { // NOI18N
String subFolder = folderName.substring (0, dotPos);
folderName = folderName.substring (dotPos + 1);
FileObject existingFile = folder.getFileObject (subFolder);
if (existingFile != null) {
if (!existingFile.isFolder ()) {
TopManager.getDefault ().notify (
new NotifyDescriptor.Message (
java.text.MessageFormat.format (DataObject.getString ("MSG_FMT_FileExists"), new Object[] { subFolder, folder.getName () }),
NotifyDescriptor.WARNING_MESSAGE
)
);
return;
}
folder = existingFile;
} else {
if (!Utilities.isJavaIdentifier (subFolder)) {
throw new IOException(
java.text.MessageFormat.format (getString("EXC_WrongName"), new Object[] { subFolder } )
);
}
folder = folder.createFolder (subFolder);
}
}
if (!"".equals (folderName)) { // NOI18N
FileObject existingFile = folder.getFileObject (folderName);
if (existingFile != null) {
if (existingFile.isFolder ()) {
TopManager.getDefault ().notify (
new NotifyDescriptor.Message (
java.text.MessageFormat.format (DataObject.getString ("MSG_FMT_FolderExists"), new Object[] { folderName, folder.getName () }),
NotifyDescriptor.INFORMATION_MESSAGE
)
);
} else {
TopManager.getDefault ().notify (
new NotifyDescriptor.Message (
java.text.MessageFormat.format (DataObject.getString ("MSG_FMT_FileExists"), new Object[] { folderName, folder.getName () }),
NotifyDescriptor.WARNING_MESSAGE
)
);
}
return;
}
if (!Utilities.isJavaIdentifier (folderName)) {
throw new IOException(
java.text.MessageFormat.format (getString("EXC_WrongName"), new Object[] { folderName} )
);
}
DataObject created = DataObject.find(folder.createFolder (folderName));
if (created != null) {
TopManager.getDefault().getLoaderPool().fireOperationEvent(
new OperationEvent.Copy (created, DataFolder.this), OperationEvent.TEMPL
);
}
}
}
}
}
/** Paste types for data objects.
*/
private abstract class Paste extends PasteType {
private String resName;
private DataObject[] objs;
private boolean clearClipboard;
private String helpCtxSuffix;
/** @param resName resource name for the name
* @param objs objects to work with
* @param clear true if we should clear clipboard
* @param helpCtxSuffix extra info token for context help
*/
public Paste (String resName, DataObject[] objs, boolean clear, String helpCtxSuffix) {
this.resName = resName;
this.objs = objs;
this.clearClipboard = clear;
this.helpCtxSuffix = helpCtxSuffix;
//System.err.println ("Creating paste: resName=" + resName + " objs.length=" + objs.length);
}
/** The name is obtained from the bundle.
* @return the name
*/
public String getName () {
return DataObject.getString (resName);
}
public HelpCtx getHelpCtx () {
return new HelpCtx (Paste.class.getName () + "." + helpCtxSuffix); // NOI18N
}
/** Paste.
*/
public final Transferable paste () throws IOException {
for (int i = 0; i < objs.length; i++)
handle (objs[i]);
// clear clipboard or preserve content
return clearClipboard ? ExTransferable.EMPTY : null;
}
/** Handles the right action
* @param obj the data object to operate on
*/
protected abstract void handle (DataObject obj) throws IOException;
}
/** Paste types for data objects.
*/
private final class SerializePaste extends PasteType {
private InstanceCookie cookie;
/**
* @param obj object to work with
*/
public SerializePaste (InstanceCookie cookie) {
this.cookie = cookie;
}
/** The name is obtained from the bundle.
* @return the name
*/
public String getName () {
return DataObject.getString ("PT_serialize");
}
public HelpCtx getHelpCtx () {
return new HelpCtx (SerializePaste.class);
}
/** Paste.
*/
public final Transferable paste () throws IOException {
String name = cookie.instanceName ();
int i = name.lastIndexOf ('.') + 1;
if (i != 0 && i != name.length ()) {
name = name.substring (i);
}
name = FileUtil.findFreeFileName (getPrimaryFile (), name, "ser"); // NOI18N
final NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine (
DataObject.getString ("SerializeBean_Text"),
DataObject.getString ("SerializeBean_Title")
);
nd.setInputText (name);
if (NotifyDescriptor.OK_OPTION == TopManager.getDefault ().notify (nd)) {
getPrimaryFile ().getFileSystem ().runAtomicAction (new FileSystem.AtomicAction () {
public void run () throws IOException {
FileObject fo = getPrimaryFile ().createData (nd.getInputText (), "ser"); // NOI18N
FileLock lock = fo.lock ();
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream (
new java.io.BufferedOutputStream (fo.getOutputStream (lock))
);
oos.writeObject (cookie.instanceCreate ());
} catch (ClassNotFoundException e) {
throw new IOException (e.getMessage ());
} finally {
if (oos != null) oos.close ();
lock.releaseLock ();
}
}
});
}
// preserve clipboard
return null;
}
}
/** Paste types for data objects.
*/
private final class InstantiatePaste extends PasteType {
private InstanceCookie cookie;
/**
* @param obj object to work with
*/
public InstantiatePaste (InstanceCookie cookie) {
this.cookie = cookie;
}
/** The name is obtained from the bundle.
* @return the name
*/
public String getName () {
return DataObject.getString ("PT_instance");
}
public HelpCtx getHelpCtx () {
return new HelpCtx (InstantiatePaste.class);
}
/** Paste.
*/
public final Transferable paste () throws IOException {
String name = cookie.instanceName ();
final NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine (
DataObject.getString ("InstanceClass_Text"),
DataObject.getString ("InstanceClass_Title")
);
nd.setInputText (name);
if (NotifyDescriptor.OK_OPTION == TopManager.getDefault ().notify (nd)) {
name = nd.getInputText ();
if (name.equals ("")) { // NOI18N
name = null;
}
try {
// create the instance
InstanceDataObject.create (DataFolder.this, name, cookie.instanceClass ());
} catch (ClassNotFoundException ex) {
throw new IOException (ex.getMessage ());
}
}
// preserve clipboard
return null;
}
}
}
/*
* Log
* 19 Gandalf 1.10.1.7 11/12/98 Jaroslav Tulach
* 18 Gandalf 1.10.1.6 11/09/98 Ian Formanek
* 17 Gandalf 1.10.1.5 11/05/98 Jaroslav Tulach Special properties for
* Folder.
* 16 Gandalf 1.10.1.4 11/05/98 Jaroslav Tulach Sorting and ordering of
* DataFolders.
*
* 15 Gandalf 1.10.1.3 11/04/98 Jaroslav Tulach
* 14 Gandalf 1.10.1.2 11/03/98 Jaroslav Tulach
* 13 Gandalf 1.10.1.1 10/30/98 Jaroslav Tulach
* 12 Gandalf 1.10.1.0 10/30/98 Jaroslav Tulach
* 11 Tuborg 1.10 10/26/98 Ian Formanek Context Help updated
* 10 Tuborg 1.9 10/08/98 Jaroslav Tulach Rename, delete, new
* disabled on read only
* folders.
*
* 9 Tuborg 1.8 09/30/98 Ian Formanek Enhanced "paste-if-exists"
* functionality - the "_1"
* suffix is added to the
* existing name.
* 8 Tuborg 1.7 08/21/98 Jaroslav Tulach serialVersionUID + compiles
* 7 Tuborg 1.6 08/21/98 Jaroslav Tulach serialVersionUID
* 6 Tuborg 1.5 07/28/98 Petr Hamernik search cookie - hierarchy
* 5 Tuborg 1.4 07/22/98 Petr Hamernik Search cookie first
* implementation
* 4 Tuborg 1.3 06/28/98 Jaroslav Tulach Delete allowed for nonroot
* fodlers.
*
* 3 Tuborg 1.2 06/22/98 Petr Hamernik bugfix 235
* 2 Tuborg 1.1 06/15/98 Ian Formanek
* 1 Tuborg 1.0 06/11/98 David Peroutka
* $
* Beta Change History:
* 0 Tuborg 1.00 --/--/98 Jaroslav Tulach Total redesign
* 0 Tuborg 1.01 --/--/98 Jaroslav Tulach copy operation works
* 0 Tuborg 1.02 --/--/98 Jaroslav Tulach rename operation
* 0 Tuborg 1.03 --/--/98 Petr Hamernik compilation
* 0 Tuborg 1.04 --/--/98 Jan Formanek help context
* 0 Tuborg 1.05 --/--/98 Jan Formanek reflecting changes in cookies
*/